home *** CD-ROM | disk | FTP | other *** search
/ Nebula 2 / Nebula Two.iso / Apps / SoundApps / TimeWarp / Source / DACPlayer.m < prev    next >
Encoding:
Text File  |  1995-06-12  |  12.6 KB  |  472 lines

  1. /*
  2.  * DACPlayer.m
  3.  * Implementation of an object to play sound over the soundout device (DACs).
  4.  * 
  5.  * You may freely copy, distribute, and reuse the code in this example.
  6.  * NeXT disclaims any warranty of any kind, expressed or  implied, as to its
  7.  * fitness for any particular use.
  8.  *
  9.  * Written by: Robert Poor
  10.  * Edit History (most recent edits first):
  11.  *
  12.  * Version II created: Sep/92
  13.  * Version I created Dec/89
  14.  *
  15.  * End of Edit History
  16.  */
  17.  
  18. #import <mach.h>
  19. #import <servers/netname.h>
  20. #import <appkit/Application.h>
  21. #import <appkit/Panel.h>
  22. #import <sound/accesssound.h>
  23. #import <sound/sounddriver.h>
  24. #import <sound/soundstruct.h>
  25. #import "DACPlayer.h"
  26. #import "errors.h"
  27. #import <c.h>        /* for MAX */
  28.  
  29. @interface DACPlayer(DACPlayerPrivate)
  30. - _setState:(Pla_state_t)newState;
  31. - _updateParameters;
  32. - _enqueueRegions;
  33. - _completedRegion;
  34. @end
  35.  
  36. /*
  37.  * Following are the routines that snddriver_reply_handler will
  38.  * dispatch to. Note that in each case, the DACPlayer object itself
  39.  * will be passed as the first argument.
  40.  */
  41.  
  42. static void DACCompletedMsg(void *arg, int tag)
  43. {
  44.   [(id)arg _completedRegion];
  45. }
  46.  
  47. /*
  48.  * The dispatch table that snddriver_reply_handler uses.
  49.  *
  50.  * ### rpoor Sep 92: It appears that the sound driver doesn't post
  51.  * ### underrun messages.  I tried everything and never got notified...
  52.  */
  53. static snddriver_handlers_t dacHandlers = {
  54.   (void *)0,        /* user-defined arg passed to dispatch functions */
  55.   (int) 0,        /* timeout */
  56.   NULL,            /* DACStartedMsg */
  57.   DACCompletedMsg,
  58.   NULL,            /* DACAbortedMsg */
  59.   NULL,            /* DACPausedMsg */
  60.   NULL,            /* DACResumedMsg */
  61.   NULL,            /* DACUnderrunMsg */
  62.   NULL,
  63.   NULL,
  64.   NULL,
  65.   NULL
  66.   };
  67.   
  68. /* 
  69.  * HandleDACMessage is called courtesy of the DPSAddPort mechanism
  70.  * whenever a message arrives from the sound driver.  userData is bound
  71.  * to the DACPlayer object itself.
  72.  */
  73. static void HandleDACMessage(msg_header_t *msg, void *userData)
  74. {
  75.   int r;
  76.  
  77.   dacHandlers.arg = userData;    /* Install dacPlayer as arg to handlers */
  78.   r = snddriver_reply_handler(msg, &dacHandlers);
  79.   checkMachError((id)dacHandlers.arg, r, "Can't parse message from DAC");
  80. }
  81.  
  82.  
  83. @implementation DACPlayer:Object
  84.  
  85. - init
  86. {
  87.   self = [super init];
  88.  
  89.   playerState = PLA_STOPPED;
  90.   regionsQueued = bytesPlayed = bytesQueued = 0;
  91.   [self setSamplingRate:SND_RATE_HIGH];
  92.   [self setRegionSize:4*vm_page_size andCount:3];
  93.  
  94.   return self;
  95. }
  96.  
  97. - free
  98. {
  99.   return [super free];
  100. }
  101.  
  102. /***
  103.  *** METHODS THAT CONTROL DACPLAYER
  104.  ***/
  105.  
  106. - prepare
  107. /*
  108.  * grab all the required resources, queue up the initial regions
  109.  * (this WILL call the delegate playData::: method), and set state
  110.  * to PLA_PAUSED.
  111.  */
  112. {
  113.   port_t arbitration_port;
  114.   int r, rate, protocol;
  115.  
  116.   [self stop];            /* make sure playing has stopped first. */
  117.  
  118.   /* get the ports that we need */
  119.   r = SNDAcquire(SND_ACCESS_OUT, 0, 0, 0, NULL_NEGOTIATION_FUN,
  120.           0, &devicePort, &ownerPort);
  121.   if (!checkMachError(self,r,"Can't acquire SoundOut resouces"))
  122.     return nil;
  123.  
  124.   arbitration_port = ownerPort;
  125.   r = snddriver_set_sndout_owner_port(devicePort,ownerPort,&arbitration_port);
  126.   if (!checkSnddriverError(self,r,"Cannot become owner of sound-out resources"))
  127.     return nil;
  128.  
  129.   r = snddriver_set_ramp(devicePort,0);        /* disable ramping */
  130.   checkSnddriverError(self,r,"Call to disable ramp failed");    /* not fatal */
  131.  
  132.   /* 
  133.    * Tell the delegate (if any) that we are about to start playing.
  134.    * Call it here in case the delegate wants to configure any of the
  135.    * dacPlayer parameters (samplingRate, regionSize, regionCount).
  136.    */
  137.   if (flags.willPlay) {
  138.     [delegate willPlay :self];
  139.   }
  140.   [self _updateParameters];
  141.  
  142.   /* set up the DMA read stream  */
  143.   protocol = 0;
  144.   rate = ((samplingRate == SND_RATE_HIGH)?
  145.     SNDDRIVER_STREAM_TO_SNDOUT_44:
  146.     SNDDRIVER_STREAM_TO_SNDOUT_22);
  147.   r = snddriver_stream_setup(devicePort,
  148.                  ownerPort,
  149.                  rate,
  150.                  READ_BUF_SIZE,
  151.                  sizeof(short),
  152.                  LOW_WATER,
  153.                  HIGH_WATER,
  154.                  &protocol,        /* ignored for sndout */
  155.                  &streamPort);
  156.   if (!checkSnddriverError(self,r,"Cannot set up stream to sound-out"))
  157.     return nil;
  158.  
  159.   /* allocate a port for the replies */
  160.   r = port_allocate(task_self(),&replyPort);
  161.   if (!checkMachError(self,r,"Cannot allocate reply port"))
  162.     return nil;
  163.  
  164.   /* Start the DMA stream in a paused state */
  165.   r = snddriver_stream_control(streamPort,0,SNDDRIVER_PAUSE_STREAM);
  166.   if (!checkSnddriverError(self,r,"can't do initial pause"))
  167.     return nil;
  168.  
  169.   /*
  170.    * Queue up the initial buffers before starting.
  171.    */
  172.   bytesPlayed = 0;
  173.   bytesQueued = 0;
  174.   regionsQueued = 0;
  175.   [self _enqueueRegions];
  176.  
  177.   /*
  178.    * Set things up to call HandleDACMessage whenever we receive a
  179.    * message on the replyPort.  (We don't expect ro recieve any message
  180.    * until we unpause the stream, see the -run method for that...)
  181.    */
  182.   DPSAddPort(replyPort,        
  183.          HandleDACMessage,        /* function to call */
  184.          MSG_SIZE_MAX,    
  185.          self,            /* second arg to HandleDACMessage */
  186.          NX_MODALRESPTHRESHOLD    /* priority */
  187.          );
  188.  
  189.   [self _setState:PLA_PAUSED];
  190.  
  191.   return self;
  192. }
  193.  
  194. - run
  195. {
  196.   int r;
  197.  
  198.   if (playerState == PLA_RUNNING) {
  199.     return nil;            /* already running.  Ignore the bum */
  200.   } else if (playerState == PLA_STOPPED) {
  201.     [self prepare];        /* starting cold.  prepare first then run */
  202.   } else if (playerState == PLA_STOPPING) {
  203.     [[self stop] prepare];    /* stop first, prepare, then run... */
  204.     return nil;
  205.   } /* else playerState == PLA_PAUSED */
  206.  
  207.   /*
  208.    * At this point we know that state = PLA_PAUSED, any initial buffers
  209.    * are already queued up, and the stream is paused.  We simply resume
  210.    * the stream to start things going...
  211.    */
  212.   r = snddriver_stream_control(streamPort,0,SNDDRIVER_RESUME_STREAM);
  213.   if (!checkSnddriverError(self,r,"Can't resume the DMA stream"))
  214.     return nil;
  215.  
  216.   return [self _setState:PLA_RUNNING];
  217. }
  218.  
  219. - pause
  220. {
  221.   int r;
  222.  
  223.   if (playerState == PLA_PAUSED) {
  224.     return nil;
  225.   } else if (playerState == PLA_STOPPED) {
  226.     return [self prepare];
  227.   } else if (playerState == PLA_STOPPING) {
  228.     return [[self stop] prepare];
  229.   } /* else playerState == PLA_RUNNING */
  230.  
  231.   r = snddriver_stream_control(streamPort,WRITE_TAG,SNDDRIVER_PAUSE_STREAM);
  232.   checkSnddriverError(self,r,"Call to pause stream failed");
  233.  
  234.   return [self _setState:PLA_PAUSED];
  235. }
  236.  
  237. - stop
  238. /*
  239.  * Terminate all playing right now.
  240.  */
  241. {
  242.   int r;
  243.  
  244.   if (playerState == PLA_STOPPED) {
  245.     return nil;
  246.   }
  247.  
  248.   /* flush any outstanding buffers */
  249.   r = snddriver_stream_control(streamPort,
  250.                    WRITE_TAG,
  251.                    SNDDRIVER_ABORT_STREAM);
  252.   checkSnddriverError(self,r,"Couldn't abort stream");
  253.   DPSRemovePort(replyPort);
  254.  
  255.   r = SNDRelease(SND_ACCESS_OUT, devicePort, ownerPort);
  256.   if (!checkMachError(self,r,"Can't deallocate the owner port"))
  257.     return nil;
  258.   r = port_deallocate(task_self(),replyPort);
  259.   if (!checkMachError(self,r,"Can't deallocate the reply port"))
  260.     return nil;
  261.  
  262.   /* 
  263.    * Tell the delegate that we stopped playing.
  264.    */
  265.   if (flags.didPlay) {
  266.     [delegate didPlay :self];
  267.   }
  268.  
  269.   bytesPlayed = bytesQueued = 0;    /* we've shut everything down */
  270.   return [self _setState:PLA_STOPPED];
  271. }
  272.  
  273.  
  274. - finish
  275. /*
  276.  * The finish method only sets the state to PLA_STOPPING.  The routines
  277.  * that handle requests from the sound driver will notice this and will
  278.  * stop enqueuing new regions.  When the last region has been played,
  279.  * dacPlayer will call [self stop] to shut things down.
  280.  */
  281. {
  282.   if (playerState == PLA_STOPPED) {
  283.     return nil;            /* already stopped */
  284.   } else if (playerState == PLA_STOPPING) {
  285.     return nil;            /* already stopping */
  286.   } /* else (playerState == PLA_RUNNING) || (playerState == PLA_PAUSED) */
  287.   
  288.   return [self _setState:PLA_STOPPING];
  289. }
  290.  
  291.  
  292. /***
  293.  *** METHODS THAT CONFIGURE DACPLAYER
  294.  ***/
  295.  
  296. - delegate { return delegate; }
  297. - setDelegate:anObject
  298. {
  299.   delegate = anObject; 
  300.   /* make note of which methods the delegate responds do */
  301.   flags.willPlay = [delegate respondsTo:@selector(willPlay:)];
  302.   flags.didPlay = [delegate respondsTo:@selector(didPlay:)];
  303.   flags.playData = [delegate respondsTo:@selector(playData:::)];
  304.   flags.didChangeState = 
  305.     [delegate respondsTo:@selector(didChangeState:from:to:)];
  306.   return self;
  307. }
  308.  
  309. - (int)regionSize { return regionSize; }
  310. - (int)regionCount { return regionCount; }
  311. - setRegionSize:(int)size andCount:(int)count
  312. {
  313.   int pages;
  314.  
  315.   /* round up to nearest vm_page_size boundary */
  316.   pages = (size+vm_page_size-1)/vm_page_size;
  317.   newRegionSize = MAX(1,pages)*vm_page_size;
  318.   newRegionCount = MAX(1,count);
  319.   /* update the new parameters if it's safe... */
  320.   return [self _updateParameters];
  321. }
  322.  
  323. - (int)samplingRate { return samplingRate; }
  324. - setSamplingRate:(int)aRate
  325. {
  326.   if (aRate < (SND_RATE_HIGH+SND_RATE_LOW)/2) {
  327.     newSamplingRate = SND_RATE_LOW;
  328.   } else {
  329.     newSamplingRate = SND_RATE_HIGH;
  330.   }
  331.   /* update the new parameters if it's safe... */
  332.   return [self _updateParameters];
  333. }
  334.  
  335. /***
  336.  *** METHODS THAT QUERY DACPLAYER
  337.  ***/
  338.  
  339. - (Pla_state_t)playerState { return playerState; }
  340.  
  341. #define BYTES_PER_SAMPLE    (sizeof(short))
  342. #define    BYTES_PER_FRAME        (BYTES_PER_SAMPLE * 2)
  343.  
  344. - (int)bytesPlayed { return bytesPlayed; }
  345. - (int)samplesPlayed { return bytesPlayed / BYTES_PER_SAMPLE; }
  346. - (int)framesPlayed { return bytesPlayed / BYTES_PER_FRAME; };
  347. - (double)secondsPlayed
  348. {
  349.   return (double)bytesPlayed / (double)(samplingRate * BYTES_PER_FRAME);
  350. }
  351.  
  352. - (int)bytesQueued { return bytesQueued; }
  353. - (int)samplesQueued { return bytesQueued / BYTES_PER_SAMPLE; }
  354. - (int)framesQueued { return bytesQueued / BYTES_PER_FRAME; }
  355. - (double)secondsQueued
  356. {
  357.   return (double)bytesQueued / (double)(samplingRate * BYTES_PER_FRAME);
  358. }
  359.  
  360.  
  361. /***
  362.  *** Internal methods
  363.  ***/
  364.  
  365. - _setState:(Pla_state_t)newState
  366. /*
  367.  * Set the state to newState.  If there's a delegate that wants to know
  368.  * about the change in state, tell 'em.  Returns self.
  369.  */
  370. {
  371.   Pla_state_t prevState = playerState;
  372.  
  373.   playerState = newState;
  374.   if (flags.didChangeState) {
  375.     [delegate didChangeState:self from:prevState to:newState];
  376.   }
  377.   return self;
  378. }
  379.  
  380. - _updateParameters
  381. /*
  382.  * If the state == PLA_STOPPED, update the "pending" new parameters.
  383.  * We'll always call this method in -prepare before starting.
  384.  */
  385. {
  386.   if (playerState == PLA_STOPPED) {
  387.     samplingRate = newSamplingRate;
  388.     regionCount = newRegionCount;
  389.     regionSize = newRegionSize;
  390.   }
  391.   return self;
  392. }
  393.  
  394. - _enqueueRegions
  395. /*
  396.  * This is the method that sends buffers to the sound driver stream.
  397.  *
  398.  * Unless state == STOPPING, it writes regions to the sound driver until
  399.  * regionsQueued == regionCount.  Each region is passed to the delegate
  400.  * in a playData::: method before being handed off to the sound driver.
  401.  */
  402. {
  403.   int r;
  404.   char *currentRegion;
  405.  
  406.   while ((playerState != PLA_STOPPING) && (regionsQueued < regionCount)) {
  407.  
  408.     /* allocate a region */
  409.     r = vm_allocate(task_self(),(vm_address_t)currentRegion,regionSize,TRUE);
  410.  
  411.     /* let the delegate have its way with the regrion */
  412.     if (flags.playData) {
  413.       [delegate playData :self :currentRegion :regionSize];
  414.     }
  415.     
  416.     /* enqueue the buffer in the DMA stream to the DACs */
  417.     r = snddriver_stream_start_writing
  418.     (streamPort,        /* port */
  419.       currentRegion,    /* data to be played */
  420.       regionSize/sizeof(short),    /* number of samples to play */
  421.       WRITE_TAG,        /* tag for this region */
  422.       FALSE,        /* preempt */
  423.       TRUE,            /* deallocate on completion */
  424.       FALSE,        /* send msg when started */
  425.       TRUE,            /* send msg when completed */
  426.       FALSE,        /* send msg when aborted */
  427.       FALSE,        /* send msg when paused */
  428.       FALSE,        /* send msg when resumed */
  429.       FALSE,        /* send msg when underflowed */
  430.       replyPort        /* port for the above messages */
  431.       );
  432.     checkSnddriverError(self,r,"Cannot enqueue write request");
  433.     regionsQueued += 1;
  434.     bytesQueued += regionSize;
  435.   }
  436.   return self;
  437. }
  438.  
  439. - _completedRegion
  440. /*
  441.  * This method is called whenever we get a "region completed" message from
  442.  * the sound driver.  (How?  The sound driver posts a message on replyPort,
  443.  * which is noticed by the DPSAddPort mechanism which calls HandleDACMessage
  444.  * which calls snddriver_reply_handler which dispatches to DACCompletedMessage
  445.  * which (finally) calls _completedRegion.)  Got that?
  446.  *
  447.  * Oh, what's this method DO?  It makes note of the fact that another region
  448.  * has been consumed by the DAC stream and calls _enqueueRegions to replenish
  449.  * the region queue.  If state == PLA_STOPPING and the region queue has hit
  450.  * zero, it means that we've just played the last region and we call 
  451.  * [self stop] to shut things down.
  452.  */
  453. {
  454.   /* bump bytesPlayed to reflect the fact that the sound driver played 'em */
  455.   bytesPlayed += regionSize;
  456.  
  457.   /* note that one region has been consumed by the sound driver. */
  458.   regionsQueued -= 1;
  459.   
  460.   /* replenish the queue of regions */
  461.   [self _enqueueRegions];
  462.  
  463.   /* shut down the machinery if we're stopping */
  464.   if ((playerState == PLA_STOPPING) && (regionsQueued == 0)) {
  465.     [self stop];
  466.   }
  467.   return self;
  468. }
  469.  
  470.  
  471. @end
  472.